import Communication
import struct, socket, time, threading
import tkinter as tk
import tkinter.ttk as ttk


# 登录页面
class Login:
    def __init__(self, master):
        self.master = ttk.Frame(master)
        master.add(self.master, text='Login')
        self._client = None
        self._stateConnect = False
        # ip
        self.varIp=tk.StringVar(value='localhost')
        ttk.Label(
            master = self.master, 
            text='IP:').place(x=10,y=30)
        ttk.Entry(
            master = self.master, 
            textvariable=self.varIp).place(x=80,y=30)
        # port
        self.varPort=tk.StringVar(value='9999')
        ttk.Label(
            master = self.master, 
            text='PORT:').place(x=10,y=60)
        ttk.Entry(
            master = self.master, 
            textvariable=self.varPort).place(x=80,y=60)
        # connect
        ttk.Button(
            master = self.master, 
            text='connect', 
            command=self.connect
            ).place(x=20,y=110)
        # 刷新
        ttk.Button(
            master = self.master, 
            text='refresh', 
            command=self.refresh
            ).place(x=120,y=110)
        # 显示连接状态的文字
        self.verText = tk.StringVar()
        self.verText.set("未连接")
        ttk.Label(
            master = self.master,
            textvariable=self.verText,
        ).place(x=20,y=150)
        # 连接状态检查线程
        threadRecv = threading.Thread(target=self.check_heartbeat)
        threadRecv.setDaemon(True)
        threadRecv.start()

    # 心跳线程的入口函数
    def check_heartbeat(self):
        while True:
            time.sleep(0.05)
            if self._stateConnect:
                if not self._client.stateHeartbeat:
                    self._stateConnect = False
                    self.verText.set("未连接（服务端断开连接）")

    # 连接按钮的回调函数
    def connect(self):
        if self._stateConnect:
            self.verText.set("已连接（再点也没用）")
        else:
            try:
                self._client = Communication.Client()
            except ConnectionRefusedError as e:
                self._stateConnect = False
                self._client = None
                self.verText.set(e)
            else:
                self.verText.set("已连接")
                self._stateConnect = True
                self.refresh()

    # 刷新文件的回调函数
    def refresh(self):
        if self._stateConnect:
            self.get_object_information()
        
    # 发送获得可设置对象请求
    def get_object_information(self):
        self._client.send(struct.pack('=I', 0x00000001)) # mark = 0
    
    # 发送和接收都需要通过访问这个连接，使用前需判断是否为None
    @property
    def client(self):
        return self._client

# 变量页面
class SettableObject:
    def __init__(self, master, pagename, client):
        self.master = ttk.Frame(master)
        master.add(self.master, text=pagename)
        self.y = 30
        self.obj = {}
        self._client = client

    def get_y(self):
        y = self.y
        self.y += 65
        return y

    def set_obj(self, objmark, objtype, objname, objdata):
        if objmark in self.obj:
            y = self.obj[objmark]['y']
            self.obj[objmark]['labelName'].destroy()
            self.obj[objmark]['labelValue'].destroy()
            self.obj[objmark]['entry'].destroy()
            self.obj[objmark]['button'].destroy()
        else:
            y = self.get_y()
        # 名字
        labelName = ttk.Label(
            master = self.master, 
            text=objname, 
            )
        labelName.place(x=10,y=y+10)

        

        # 输入框
        if objtype is '?':
            # 值
            if objdata:
                labelValue = ttk.Label(
                    master = self.master, 
                    text = 'Enable',
                    )
                stringVar = tk.IntVar()
                stringVar.set(1)
            else:
                labelValue = ttk.Label(
                    master = self.master, 
                    text = 'Disable',
                    )
                stringVar = tk.IntVar()
                stringVar.set(0)
            labelValue.place(x=60,y=y)
            
            entry = ttk.Checkbutton(
                master = self.master, 
                text = 'select enable', 
                variable = stringVar, 
                onvalue = 1, 
                offvalue = 0)
            entry.place(x=60,y=y+20)
        else:
            # 值
            labelValue = ttk.Label(
                master = self.master, 
                text = str(objdata),
                )
            labelValue.place(x=60,y=y)
            stringVar = tk.StringVar(value=str(objdata))
            entry = ttk.Entry(
                master = self.master, 
                textvariable=stringVar
                )
            entry.place(x=60,y=y+20)

        # 发送摁键
        button = ttk.Button(
            master = self.master, 
            text='confirm', 
            command=lambda :self._send(objmark),
            )
        button.place(x=250,y=y+10)

        self.obj[objmark] = {
            'type' : objtype, 
            'name' : objname,
            'data' : objdata,
            'stringVar': stringVar,
            'labelName' : labelName,
            'labelValue' : labelValue,
            'entry' : entry,
            'button' : button,
            'y' : y,
            }

    def _send(self, mark):
        if self.obj[mark]['type'] is '?':
            if self.obj[mark]['stringVar'].get()==1:
                self._client.send(struct.pack('=IIc', mark, 1, b'1'))
            else:
                self._client.send(struct.pack('=IIc', mark, 1, b'0'))
        elif self.obj[mark]['type'] is 'i':
            self._client.send(struct.pack('=IIi', mark, 4, int(self.obj[mark]['stringVar'].get())))
        elif self.obj[mark]['type'] is 'f':
            self._client.send(struct.pack('=IIf', mark, 4, float(self.obj[mark]['stringVar'].get())))
        elif self.obj[mark]['type'] is 'd':
            self._client.send(struct.pack('=IId', mark, 8, float(self.obj[mark]['stringVar'].get())))
        elif self.obj[mark]['type'] is 's':
            string = self.obj[mark]['stringVar'].get().encode()
            self._client.send(struct.pack('=II', mark, len(string))+string)



class App(ttk.Frame):
    def __init__(self, master=None):
        # 窗口设定
        ttk.Frame.__init__(self, master)
        self.master.title('YouQixiaowu')
        self.master.geometry('400x400+100+100')
        self.master.minsize(400, 400)
        style = ttk.Style()
        style.configure("BW.TLabel", font=("Times", "10",'bold'))
        #self.master.resizable(width=False, height=False)
        # 构建基础页面
        self.noetbook = ttk.Notebook(self.master)
        self.noetbook.place(relx=0.0, rely=0.0, relwidth=1.0, relheight=1.0)
        # 添加连接页面
        self.objLogin = Login(self.noetbook)
        # 变量页面
        self.pages = {} # name, SettableObject
        # 接收消息线程
        threadRecv = threading.Thread(target=self.recvloop)
        threadRecv.setDaemon(True)
        threadRecv.start()
        # 绘制循环
        self.master.mainloop()


    def recvloop(self):
        while True:
            # 未连接的话，休眠
            if self.objLogin.client is None: 
                time.sleep(0.1)
                continue
            # 已连接的话，接收
            data = self.objLogin.client.recv()
            # 断开连接
            if data is False: 
                time.sleep(0.1)
                continue 
            # 收到空消息
            if data is None: 
                continue
            index = 0
            # 接收名字
            lenStr = struct.unpack('I', data[index:index+4])[0]
            objname = data[index+4:index+4+lenStr].decode()
            index += 4 + lenStr
            # 接收所在页面名字
            lenStr = struct.unpack('I', data[index:index+4])[0]
            objpage = data[index+4:index+4+lenStr].decode()
            index += 4 + lenStr
            if objpage not in self.pages:
                self.pages[objpage] = SettableObject(self.noetbook, objpage, self.objLogin.client)
            # 接收mark
            objmark = struct.unpack('I', data[index:index+4])[0]
            index += 4
            # 接收类型
            objtype = data[index:index+1].decode()
            index +=1
            # 接收值
            objdata = None
            if objtype is '?':
                objdata = struct.unpack('?', data[index:index+1])[0]
            elif objtype is 'i':
                objdata = struct.unpack('i', data[index:index+4])[0]
            elif objtype is 'f':
                objdata = struct.unpack('f', data[index:index+4])[0]
            elif objtype is 'd':
                objdata = struct.unpack('d', data[index:index+8])[0]
            elif objtype is 's':
                lendata = struct.unpack('I', data[index:index+4])[0]
                objdata = struct.unpack(str(lendata)+'s', 
                    data[index+4:index+4+lendata])[0].decode()
            else:
                print("异常数据", objtype)
                continue
            self.pages[objpage].set_obj(objmark, objtype, objname, objdata)
            print(objpage, objname, objmark, objdata)
                


if __name__ == "__main__":
    App()
